<!DOCTYPE html>
<html lang="zh-Hans">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.3.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/blog/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/blog/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/blog/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/blog/images/logo.svg" color="#222">

<link rel="stylesheet" href="/blog/css/main.css">

<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&display=swap&subset=latin,latin-ext">
<link rel="stylesheet" href="/blog/lib/font-awesome/css/font-awesome.min.css">


<script id="hexo-configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    hostname: new URL('https://guodh.gitee.io/blog').hostname,
    root: '/blog/',
    scheme: 'Pisces',
    version: '7.7.1',
    exturl: false,
    sidebar: {"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},
    copycode: {"enable":true,"show_result":true,"style":null},
    back2top: {"enable":true,"sidebar":false,"scrollpercent":false},
    bookmark: {"enable":false,"color":"#222","save":"auto"},
    fancybox: false,
    mediumzoom: false,
    lazyload: false,
    pangu: false,
    comments: {"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},
    algolia: {
      appID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    },
    localsearch: {"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},
    path: 'search.xml',
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}
  };
</script>

  <meta name="description" content="分享的目标   了解Rocketmq的架构 了解到Rocketmq内部主要使用的技术手段 Rocketmq拥有的不同的feature 介绍一下其他的消息队列">
<meta property="og:type" content="article">
<meta property="og:title" content="RocketMq是如何跑起来的">
<meta property="og:url" content="https://guodh.gitee.io/blog/2021/01/10/2021/RocketMq%E6%98%AF%E5%A6%82%E4%BD%95%E8%B7%91%E8%B5%B7%E6%9D%A5%E7%9A%84/index.html">
<meta property="og:site_name" content="蛋蛋的小屋">
<meta property="og:description" content="分享的目标   了解Rocketmq的架构 了解到Rocketmq内部主要使用的技术手段 Rocketmq拥有的不同的feature 介绍一下其他的消息队列">
<meta property="og:locale">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101615739496.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101642982704.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102425341031.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101698621878.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102523135991.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101710184308.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101702852631.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101719368795.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101721743867.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101725680969.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101729309256.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101763000202.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101769234208.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101764558591.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101771374597.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102053649739.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102452396816.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102487397347.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102487592817.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102496821515.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102496992033.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102522455673.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102550327265.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102558617023.jpg">
<meta property="og:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102559971296.jpg">
<meta property="article:published_time" content="2021-01-09T16:00:01.000Z">
<meta property="article:modified_time" content="2021-01-10T15:28:54.023Z">
<meta property="article:author" content="RunningEgg">
<meta property="article:tag" content="RocketMq">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101615739496.jpg">

<link rel="canonical" href="https://guodh.gitee.io/blog/2021/01/10/2021/RocketMq%E6%98%AF%E5%A6%82%E4%BD%95%E8%B7%91%E8%B5%B7%E6%9D%A5%E7%9A%84/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome: false,
    isPost: true
  };
</script>

  <title>RocketMq是如何跑起来的 | 蛋蛋的小屋</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-meta">

    <div>
      <a href="/blog/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">蛋蛋的小屋</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
        <p class="site-subtitle">来了就看看吧，没有刀剑只有故事</p>
  </div>

  <div class="site-nav-toggle">
    <div class="toggle" aria-label="Toggle navigation bar">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>
</div>


<nav class="site-nav">
  
  <ul id="menu" class="menu">
        <li class="menu-item menu-item-home">

    <a href="/blog/" rel="section"><i class="fa fa-fw fa-home"></i>Home</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/blog/about/" rel="section"><i class="fa fa-fw fa-user"></i>About</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/blog/archives/" rel="section"><i class="fa fa-fw fa-archive"></i>Archives</a>

  </li>
        <li class="menu-item menu-item-book">

    <a href="/blog/book/" rel="section"><i class="fa fa-fw fa-archive"></i>book</a>

  </li>
  </ul>

</nav>
</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content">
            

  <div class="posts-expand">
      
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block " lang="zh-Hans">
    <link itemprop="mainEntityOfPage" href="https://guodh.gitee.io/blog/2021/01/10/2021/RocketMq%E6%98%AF%E5%A6%82%E4%BD%95%E8%B7%91%E8%B5%B7%E6%9D%A5%E7%9A%84/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/blog/images/avatar.gif">
      <meta itemprop="name" content="RunningEgg">
      <meta itemprop="description" content="写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="蛋蛋的小屋">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          RocketMq是如何跑起来的
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              <span class="post-meta-item-text">Posted on</span>
              

              <time title="Created: 2021-01-10 00:00:01 / Modified: 23:28:54" itemprop="dateCreated datePublished" datetime="2021-01-10T00:00:01+08:00">2021-01-10</time>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <blockquote>
<p>分享的目标</p>
</blockquote>
<ul>
<li>了解Rocketmq的架构</li>
<li>了解到Rocketmq内部主要使用的技术手段</li>
<li>Rocketmq拥有的不同的feature</li>
<li>介绍一下其他的消息队列</li>
</ul>
<a id="more"></a>

<h2 id="RocketMq架构图"><a href="#RocketMq架构图" class="headerlink" title="RocketMq架构图"></a>RocketMq架构图</h2><p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101615739496.jpg" alt="-w1060"></p>
<h2 id="RocketMq的一些基本概念"><a href="#RocketMq的一些基本概念" class="headerlink" title="RocketMq的一些基本概念"></a>RocketMq的一些基本概念</h2><p>在了解Rocketmq之前，你需要了解Rocketmq带有的专业术语：<br><a href="https://github.com/apache/rocketmq/blob/master/docs/cn/concept.md">https://github.com/apache/rocketmq/blob/master/docs/cn/concept.md</a></p>
<p>同时在本文的附录会有一份。</p>
<h2 id="MQ-的演化历史"><a href="#MQ-的演化历史" class="headerlink" title="MQ 的演化历史"></a>MQ 的演化历史</h2><h3 id="本质是队列"><a href="#本质是队列" class="headerlink" title="本质是队列"></a>本质是队列</h3><ol>
<li>第一类队列，Java自带的：ConcurrentLinkedQueue，DelayQueue</li>
<li>第二类队列，依托于其它中间件提供的数据结构，来持久化、提升插入，读取速度：Mysql（B+Tree），Redis（Hash），RocksDB（LSM）。（实践中，也确实有使用Redis来实现消息队列的）</li>
<li>第三类队列，自己实现数据结构，针对高可用，高性能，高扩展做了自己定制化的实现：Kafka，Rocketmq</li>
</ol>
<p>MessageQueue本质上还是一个队列，队列能做的事情，它都能做。</p>
<h3 id="基础能力"><a href="#基础能力" class="headerlink" title="基础能力"></a>基础能力</h3><ol>
<li>异步-&gt; 削峰</li>
<li>持久化+重试 -&gt; 分布式事务最终一致</li>
<li>订阅关系 -&gt; 消息分发，消息总线</li>
</ol>
<p><strong>归纳：可靠的最少一次的异步调用</strong></p>
<h2 id="RocketMq的一些Feature"><a href="#RocketMq的一些Feature" class="headerlink" title="RocketMq的一些Feature"></a>RocketMq的一些Feature</h2><p>当然，借助于RocketMq良好的设计，RocketMq最最基础的Feature是以下：<br><a href="https://github.com/apache/rocketmq/blob/master/docs/cn/features.md">https://github.com/apache/rocketmq/blob/master/docs/cn/features.md</a></p>
<p>随着，时间的变化，RocketMq的Feature已经远远不止这些，借用官方文档的介绍：</p>
<ul>
<li>Messaging patterns including publish/subscribe, request/reply and streaming</li>
<li>Financial grade transactional message</li>
<li>Built-in fault tolerance and high availability configuration options base on DLedger</li>
<li>A variety of cross language clients, such as Java, C/C++, Python, Go</li>
<li>Pluggable transport protocols, such as TCP, SSL, AIO</li>
<li>Built-in message tracing capability, also support opentracing</li>
<li>Versatile big-data and streaming ecosytem integration</li>
<li>Message retroactivity by time or offset</li>
<li>Reliable FIFO and strict ordered messaging in the same queue</li>
<li>Efficient pull and push consumption model</li>
<li>Million-level message accumulation capacity in a single queue</li>
<li>Multiple messaging protocols like JMS and OpenMessaging</li>
<li>Flexible distributed scale-out deployment architecture</li>
<li>Lightning-fast batch message exchange system</li>
<li>Various message filter mechanics such as SQL and Tag</li>
<li>Docker images for isolated testing and cloud isolated clusters</li>
<li>Feature-rich administrative dashboard for configuration, metrics and monitoring</li>
<li>Authentication and authorization</li>
<li>Free open source connectors, for both sources and sinks</li>
</ul>
<h2 id="从Broker说起"><a href="#从Broker说起" class="headerlink" title="从Broker说起"></a>从Broker说起</h2><h3 id="如何保证高性能写入？"><a href="#如何保证高性能写入？" class="headerlink" title="如何保证高性能写入？"></a>如何保证高性能写入？</h3><p>总结：</p>
<ol>
<li>CommitLog顺序写，不区分Topic，所有的消息都是append到CommitLog的末尾</li>
<li>通过mmap内存映射的机制，避免了把文件读入内存的过程，在JVM直接可以定位到文件的问题。</li>
<li>写入是在Linux提供的PageCache上，即使JVM崩溃，PageCache还是保留。</li>
<li>PageCache会有预热功能，提前分配PageCache的内存，采用的方式是在PageCache（4K为一页）写入1字节的数据。</li>
<li>为了避免预热的PageCache被Linux给flush()，调用了mlock()，锁住了PageCache。</li>
</ol>
<h3 id="CommitLog-写入示意图"><a href="#CommitLog-写入示意图" class="headerlink" title="CommitLog 写入示意图"></a>CommitLog 写入示意图</h3><p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101642982704.jpg" alt="-w688"></p>
<h3 id="ConsumerQueue"><a href="#ConsumerQueue" class="headerlink" title="ConsumerQueue"></a>ConsumerQueue</h3><h4 id="格式"><a href="#格式" class="headerlink" title="格式"></a>格式</h4><p>ConsumerQueue是固定长度的，只包含了offset + size + tagsCode。size代表的是commitLog的长度。本质上，queue只是一个索引的作用。所以单个Queue可以做到百万级别。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">代码来源 ConsumeQueue#putMessagePositionInfo()....</span><br><span class="line"><span class="keyword">this</span>.byteBufferIndex.flip();</span><br><span class="line"><span class="keyword">this</span>.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE);</span><br><span class="line"><span class="keyword">this</span>.byteBufferIndex.putLong(offset);</span><br><span class="line"><span class="keyword">this</span>.byteBufferIndex.putInt(size);</span><br><span class="line"><span class="keyword">this</span>.byteBufferIndex.putLong(tagsCode);</span><br></pre></td></tr></table></figure>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102425341031.jpg" alt="-w373"></p>
<h4 id="放入过程"><a href="#放入过程" class="headerlink" title="放入过程"></a>放入过程</h4><p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101698621878.jpg" alt="-w613"><br>— 来源 DefaultMessageStore.doReput()方法</p>
<p>这里用，伪代码表示一下：</p>
<ol>
<li>会有一个异步现场，持续将写入成功的commitLog刷到queue中。</li>
<li>需要注意的是，这里还会触发 messageArrivingListener.arriving。（这里是为了触发长轮询中的socket channel，来回复来自Consumer的长轮询，后面也会讲到。）</li>
</ol>
<h4 id="落盘位置"><a href="#落盘位置" class="headerlink" title="落盘位置"></a>落盘位置</h4><p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102523135991.jpg" alt="-w1029"></p>
<p>commitLog是单个文件，而consumerQueue是按照 “Topic + queueId + 固定长度的文件” 来落库的。</p>
<h4 id="Consumer的Offset位置（消费进度）"><a href="#Consumer的Offset位置（消费进度）" class="headerlink" title="Consumer的Offset位置（消费进度）"></a>Consumer的Offset位置（消费进度）</h4><p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101710184308.jpg" alt="-w1076"></p>
<p>需要注意的是，到这里我们还是没有提到Consumer的offset存储在哪里。其实offset是放在一个单独的json文件中（见截图），也就是说数据的消费，本质上和queue是没有什么关系的。消费关注的只是一个offset的一个增长。</p>
<p>以上指的是，集群模式下的情况。如果是广播模式，还是每个Consumer自己维护了Offset。</p>
<h4 id="多个queue的设计价值"><a href="#多个queue的设计价值" class="headerlink" title="多个queue的设计价值"></a>多个queue的设计价值</h4><p>Topic下会存在多个queue，为什么需要多个queue呢？</p>
<ol>
<li>当有多个Broker组成集群的情况下，queue会均匀的分布在多个Broker上。Producer会有selector策略（一般是均衡策略，也就是每次都是queueId+1)，能够使得的消息均匀的落在两个Master上面</li>
<li>在RocketMq的设计理念中，单个的queue永远只能被一个consumer消费。（consumer在rebalance的时候，会有概率消费多个queue）这就很好的避免了并发的问题。</li>
</ol>
<p>不过，随之而来的存在的问题就是，当consumer的数量大于queue的时候，就会有部分consumer是无法消费的。</p>
<blockquote>
<p>PS: 去哪儿mq，以及滴滴开源的DDMQ，都是依靠于中间增加Proxy层，解决consumer的数量大于queue这个问题。</p>
</blockquote>
<h3 id="PageCache"><a href="#PageCache" class="headerlink" title="PageCache"></a>PageCache</h3><p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101702852631.jpg" alt="-w1132"></p>
<p>buff/cache 部分就是PageCache，但随时都有可能会被Linux回收。</p>
<p>Page cache 也叫页缓冲或文件缓冲，是由好几个磁盘块构成，大小通常为4k，在64位系统上为8k，构成的几个磁盘块在物理磁盘上不一定连续，文件的组织单位为一页， 也就是一个page cache大小，文件读取是由外存上不连续的几个磁盘块，到buffer cache，然后组成page cache，然后供给应用程序。</p>
<p>Page cache在linux读写文件时，它用于缓存文件的逻辑内容，从而加快对磁盘上映像和数据的访问。具体说是加速对文件内容的访问，buffer cache缓存文件的具体内容——物理磁盘上的磁盘块，这是加速对磁盘的访问。</p>
<h4 id="PageCache的写"><a href="#PageCache的写" class="headerlink" title="PageCache的写"></a>PageCache的写</h4><p>普通的write调用只是将数据写到page cache中，并将其标记为dirty就返回了，磁盘I/O通常不会立即执行，这样做的好处是减少磁盘的回写次数，提供吞吐率，不足就是机器一旦意外挂掉，page cache中的数据就会丢失。一般安全性比较高的程序会在每次write之后，调用fsync立即将page cache中的内容回写到磁盘中。</p>
<h4 id="PageCache的读"><a href="#PageCache的读" class="headerlink" title="PageCache的读"></a>PageCache的读</h4><p>对于数据文件的读取 ，如果一次读取文件时出现未命中PageCache的情况，OS从物理磁盘上访问读取文件的同时，会顺序对其他相邻块的数据文件进行预读取（ps：顺序读入紧随其后的少数几个页面）。这样，只要下次访问的文件已经被加载至PageCache时，读取操作的速度基本等于访问内存。</p>
<h4 id="PageCache小结"><a href="#PageCache小结" class="headerlink" title="PageCache小结"></a>PageCache小结</h4><ul>
<li>commitLog的写入，充分利用的PageCache的特性。queue写入也是类似的。</li>
<li>在commitLog和queue读取的场景中，由于单个Queue也是顺序的，Linux会预读一部分数据到PageCache中，所以Queue的读写速度写实很快的。</li>
<li>而CommitLog是通过mmap映射，会先在PageCace中查找。不同的Topic数据在commitLog相距比较远的时候，无法利用PageCache，IOPS 会飙高。（可以把读往Slave上挪，有配置）</li>
</ul>
<p>缺点：</p>
<ol>
<li>当内存不存，pageCache会被Linux回收，RT会飙高，导致毛刺现象。</li>
<li>当机器整个崩溃的时候，如果是异步刷盘模式，pageCache的数据也是会丢失的。</li>
</ol>
<h3 id="删除策略"><a href="#删除策略" class="headerlink" title="删除策略"></a>删除策略</h3><p>默认的保存时间是3天，但是会有触发机制，实际会比3天更长一些。</p>
<ol>
<li>按照commitlog最后一个文件的写入时间，大于3天，则整个删除。</li>
<li>queue类似。</li>
<li>Index 按照 commitLog 删除的offset，遍历删除。Index是K-V形式，因为Value是按照顺序排列，也是顺序删除。Index的Value为什么是顺序的，下面有讲。</li>
<li>开启定时任务每10s扫描是否有文件需要删除 + 并且满足条件5</li>
<li>有三种情况会进入删除文件操作：到了deleteWhere指定的时间点（默认是凌晨4点）、磁盘不足、手动触发</li>
</ol>
<h2 id="Producer"><a href="#Producer" class="headerlink" title="Producer"></a>Producer</h2><p>关于Producer的发送消息的过程，我们可先看下一个Demo。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101719368795.jpg" alt="-w886"></p>
<p>需要注意的点：</p>
<ol>
<li>IndexKey是messageKey，建议都是写入业务属性，方便查询，例如用 RENT_ORDER_10001</li>
<li>打印返回值，关注是否发送成功，以及返回值中会有messageId</li>
</ol>
<h3 id="Index"><a href="#Index" class="headerlink" title="Index"></a>Index</h3><p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101721743867.jpg" alt="-w727"><br>– 此图片来源网络</p>
<p>message对于Index的索引，使用的是hash算法。</p>
<p>需要注意的是，由于整个文件长度的固定的，是先放value，在放key。value对于的是preIndexOffset。而这里的phyOffset是commitLog的Offset。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101725680969.jpg" alt="-w1198"></p>
<blockquote>
<p>代码来源IndexFile.putKey()；</p>
</blockquote>
<h3 id="MessageId"><a href="#MessageId" class="headerlink" title="MessageId"></a>MessageId</h3><p>对于MessageId而言，通过解码可以直接拿到commitLog的offset的位置。<br>所以更加的便捷，但是一般只能从日志中捞到它。而Index可以自己设置，所以更加便捷。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101729309256.jpg" alt="-w449"><br>–代码来源MessageDecoder.java</p>
<h2 id="Consumer"><a href="#Consumer" class="headerlink" title="Consumer"></a>Consumer</h2><h4 id="Pull模式"><a href="#Pull模式" class="headerlink" title="Pull模式"></a>Pull模式</h4><p>在前面的名字介绍中，我们就知道RocketMq支持Pull和Push模式，我们来着重介绍下Pull模式。Pull模式，是通过长轮询的方式来来实现消息的即时通知。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101763000202.jpg" alt="-w853"></p>
<p>– 代码来源PullRequestHoldService的run()</p>
<p>看到上面这个图，可能有小伙伴对这个代码有些不解。为什么是wait(5*1000)，然后再去触发arriveMessage。其实这里wait的是处理线程，而此时socket channel是不响应状态。对于这块的知识点，可以看下Netty相关的内容。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101769234208.jpg" alt="-w1167"><br>– 代码来源 PullRequestHoldService.notifyMessageArriving()</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101764558591.jpg" alt="-w1009"><br>– 代码来源PullMessageProcessor.executeRequestWhenWakeup()</p>
<p>通过这个代码可以看到，deReput的线程拿到了阻塞的channel，并且回写了结果。</p>
<h4 id="重新投递"><a href="#重新投递" class="headerlink" title="重新投递"></a>重新投递</h4><ol>
<li>当消息消费失败，RocketMq 会将消息按照 2的幂次来进行重新投递，最多16次。</li>
<li>RocketMq 支持level级别的定时任务</li>
</ol>
<p>上述两者其实本质，都是使用相同的能力。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16101771374597.jpg" alt="-w657"><br>图中的scheduleService对应的是RocketMq中的ScheduleMessageService</p>
<ol>
<li>Consumer消费消息失败，会犯失败的消息体给Broker（此时，如果Consumer挂了不响应，offset并没有变，所以还是会重新来）</li>
<li>Broker接收到失败的确认，将Message的Topic改写，改为 SCHEDULE_TOPIC_XXX，将原来的Topic塞到Message的扩展字段里面。同时在Message字段中，保存重试的次数，已经重试的次数，将消息放到对于的Schedule队列中。</li>
<li>每个队列重新投递的时间都是按照 2 &gt;&gt; level 秒 轮询</li>
<li>消息到达时间之后，会将消息投递到 %RETRY% + ConsumerGroup 命名的 Topic 中，进行消费。（而定时任务，是回到原来的Topic中）</li>
<li>如果一直失败，会被放入死信 Topic 下，死信队列命名方式：%DLQ%+consumerGroup</li>
</ol>
<h4 id="Rebalance"><a href="#Rebalance" class="headerlink" title="Rebalance"></a>Rebalance</h4><p>我们都知道Consumer是会随时上线下线的。每当有新的Consumer加入到ConsumerGroup的时候，需要通过Rebalance来实现queue和Consumer的重新映射。</p>
<p>RocketMq提供的Rebalance策略有如下：<br><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102053649739.jpg" alt="-w484"></p>
<p>每个Consumer在启动的时候，会有一个Rebalance的Schedule，每20秒触发一下。每次都是从Broker中，拿到最新的queue和consumer的数量。</p>
<p>这样就够了吗？这也是我们组的小伙伴在分享的时候问了一个问题，Rocketmq如何保证Consumer之间数据的同步呢?这不是会造成Consumer之间数据不一致吗？</p>
<blockquote>
<p>Broker包下的 RebalanceLockManager.java</p>
</blockquote>
<p>其实，Consumer在Rebalance的时候是会向Broker发起上锁的请求。而Lock保证了Queue永远是被一个Consumer消费的。但是在Rebalance的过程中，可能会发生当个Consumer消费多个queue的情况。</p>
<p>由于Consumer能从Broker拉倒数据，并不会发生网络阻断的情况，脑裂情况也被避免。</p>
<p>在20S就有一轮的频繁Rebalance下，最终Queue会按照策略分布。</p>
<h4 id="顺序消费"><a href="#顺序消费" class="headerlink" title="顺序消费"></a>顺序消费</h4><ol>
<li>单个queue上的消息顺序的</li>
<li>Consumer的消息线程，默认是多线程启动</li>
<li>Consumer会有Rebalance过程</li>
</ol>
<p>顺序消费，就是需要解决上述三个问题。</p>
<ol>
<li>针对的问题一使用Producer特定的selector策略，可以永远只选择第一个queue</li>
<li>consumer需要单线程来消费</li>
<li>这个在Rebalance中有讲到，由于lock的存在，每个queue只会被一个consumer消费</li>
</ol>
<h4 id="tag的使用"><a href="#tag的使用" class="headerlink" title="tag的使用"></a>tag的使用</h4><p>在queue的结构体上，我们可以看到有个tagCode，这个就是tagHash之后的结果。RocketMq会在Broker端，先进行消息的初步过滤（由于存在code碰撞的情况），在client还会在过滤一些。</p>
<p>但是，大部分的数据都在Broker端过滤掉了，大大减少了无效的传输。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102452396816.jpg" alt="-w740"></p>
<h2 id="高可用部署"><a href="#高可用部署" class="headerlink" title="高可用部署"></a>高可用部署</h2><blockquote>
<p>对于任何服务而言，高可用的部署方案一定是需要的，在有条件的情况下还需要思考跨机房部署做灾备的情况。</p>
</blockquote>
<h4 id="多-Master-模式"><a href="#多-Master-模式" class="headerlink" title="多 Master 模式"></a>多 Master 模式</h4><p>需要磁盘开启 RAID 10，通过磁盘备份来保证数据的可靠性</p>
<h4 id="多Master多Slave模式-异步复制"><a href="#多Master多Slave模式-异步复制" class="headerlink" title="多Master多Slave模式-异步复制"></a>多Master多Slave模式-异步复制</h4><p>HA通过异步复制的方式，将Master数据同步到Slave上。同时，Slave提供读的能力。默认是当Master内存超过40%之后，就会让Consumer往Slave上读取数据。</p>
<p>当高频率的读并且commitLog相距比较远，无法利用PageCache，IO较高的时候也会影响到Master的写入，此时也可以通过配置，修改为Slave读。</p>
<p>缺点：</p>
<ol>
<li>Master宕机，磁盘损坏情况下会丢失少量消息。</li>
<li>无法主从切换</li>
</ol>
<h4 id="多Master多Slave模式-同步双写"><a href="#多Master多Slave模式-同步双写" class="headerlink" title="多Master多Slave模式-同步双写"></a>多Master多Slave模式-同步双写</h4><p>写入Master和Slave都成功之后，才返回写入成功。</p>
<p>缺点：</p>
<ol>
<li>性能比异步复制模式略低（大约低10%左右）</li>
<li>发送单个消息的RT会略高</li>
<li>且目前版本在主节点宕机后，备机不能自动切换为主机</li>
</ol>
<blockquote>
<p>数据来源：<a href="https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md">https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md</a></p>
</blockquote>
<h4 id="Dledger模式"><a href="#Dledger模式" class="headerlink" title="Dledger模式"></a>Dledger模式</h4><p>单个Broker集群，需要 1 Leader，多个 Follower（大于2），总体数量大于3。本质上，Dledger 利用的是Raft协议来实现Broker集群之间commitLog的复制。</p>
<p>当有一个节点发生了故障，剩余节点会再次选举，选出新的 Leader 来提供写入能力。</p>
<blockquote>
<p>关于Raft可以看下这个动画，很好的解释了Raft<br><a href="http://thesecretlivesofdata.com/raft/">http://thesecretlivesofdata.com/raft/</a></p>
</blockquote>
<p>需要说的是，由于 Leader只有一个节点，所以还是单服务器写入，同时需要同步到 n/2 + 1 节点才算，写入成功。性能应该会比同步双写略差一些。但是，多副本的 Follower 也能够提供读的能力。</p>
<p>缺点：故障发生选举的时候，由于数据不一致性存在重复消费的可能性，但不会丢数据。</p>
<h4 id="Dledger模式-VS-多Master-Slave模式"><a href="#Dledger模式-VS-多Master-Slave模式" class="headerlink" title="Dledger模式 VS 多Master-Slave模式"></a>Dledger模式 VS 多Master-Slave模式</h4><ol>
<li>多Master-Slave模式下，当Master挂了之后，NameSrv不能立刻感知，短时间还会有数据发给挂掉的服务</li>
<li>Dledger在发生选举的时候，服务也是不可用</li>
<li>一个Dledger需要3台服务器，如果是两个集群来分配写入压力，则需要6台。而多Master-Slave模式可以配置成，3主3从，4主4从……    成本上，多Master-Slave模式会比较省。</li>
</ol>
<h4 id="自己实现主从切换"><a href="#自己实现主从切换" class="headerlink" title="自己实现主从切换"></a>自己实现主从切换</h4><p>目前，看到的滴滴开源的DDMQ实现了Master-Slave的切换，入口的话是 RoleChangeState。维护了Broker的状态机，同时改了很多queue分配的策略等。略复杂，就不细看了。有兴趣的小伙伴，可以自己看下。</p>
<p>比较疑惑的是，为什么RocketMq为什么不实现这个Feature？</p>
<ol>
<li>或许这样做代码耦合的太厉害，Dledger确实对于整体的架构没有那么大的侵入性。</li>
<li>还是说多Master-Slave模式基本满足了99%的需求，剩下的没考虑。不知道阿里内部是主从切换是如何实现的。</li>
</ol>
<h4 id="跨机房的高可用"><a href="#跨机房的高可用" class="headerlink" title="跨机房的高可用"></a>跨机房的高可用</h4><p>在大公司内部，如果单一机房出现网络以及断电问题，基础服务还是必须保证可用性的。所以，跨机房的部署，以及集群之间的数据同步是必不可少的。</p>
<p>目前，已知的是阿里、网易、滴滴的DDMQ，都是实现了跨机房的高可用。由于这块没有什么实践。粘贴一些来源于网络的图片。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102487397347.jpg" alt="-w688"></p>
<blockquote>
<p>图片来源自 <a href="https://zhuanlan.zhihu.com/p/108256762">https://zhuanlan.zhihu.com/p/108256762</a></p>
</blockquote>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102487592817.jpg" alt="-w761"></p>
<blockquote>
<p>图片来源自 <a href="https://zhuanlan.zhihu.com/p/108256762">https://zhuanlan.zhihu.com/p/108256762</a></p>
</blockquote>
<p>目测，都是通过ZK做了一个服务注册，自己去实现了一个HA的Controller作为FailOver的管理者，维护着整体服务的状态机。集群之间，通过数据复制，来保证数据可靠性。</p>
<h2 id="事务消息"><a href="#事务消息" class="headerlink" title="事务消息"></a>事务消息</h2><p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102496821515.jpg" alt="-w924"></p>
<ol>
<li>投递半消息到Broker，此时Topic被改写，放入HALG_MESSAGE_TOPIC下，原始Topic被放入扩展字段<br> a. 需要注意的是，不要即使用事务消息，又设置延迟消息等级，Topic会被改写成面目全非，会出现问题</li>
<li>Producer 在Listener中执行剩下的操作，详情见图片。需要将 TransactionId 缓存，这个很重要，记笔记。</li>
<li>commit 或者 rollback</li>
<li>broker 通过 msgId，定位到 offset，将Topic改为原来的，重新拿出投递。</li>
<li>如果步骤3，没收到。则会有定时消息反查。<br> a. 需要注意的时，这时会有新的消息放到HALG_MESSAGE_TOPIC下， msgId 是新的。所以，在步骤二中需要用 TransactionId 来判断回查的业务维度。</li>
</ol>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102496992033.jpg" alt="-w811"></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> LocalTransactionState <span class="title">checkLocalTransaction</span><span class="params">(MessageExt msg)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// 这个 msgId 不可以用</span></span><br><span class="line">    String wrongMsgId = msg.getMsgId();</span><br><span class="line">    Integer status = localTrans.get(msg.getTransactionId());</span><br><span class="line">    <span class="keyword">return</span> LocalTransactionState.ROLLBACK_MESSAGE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="opMessage"><a href="#opMessage" class="headerlink" title="opMessage"></a>opMessage</h4><p>每次当生产者 commit 或者是 rollback 消息的时候，都需要“删除”half_message（其实只是更新到下一个offset），但是不巧的是，所有的消息都是无法被修改的，能被修改的只有offset。</p>
<p>因此，</p>
<ol>
<li>每次删除消息，其实都是提交了一个opMessage，会放到 op_half_message 上。</li>
<li>同时，如果超过了最大的，回查check次数，也会被放入 MAX_TIME_TOPIC 下面。</li>
<li>每一次的HALF_MESSAGE的回查，都是重新放回HALF_MESSAGE中，然后在去查询ProducerGroup。</li>
<li>最初是从op_half_message读 32个消息，每次putBack()又会拿出32个消息，最终op_half_message的offset之后的所有需要删除的消息的op_message都会在内存removeMap中。</li>
<li>doneList保存了已经操作过的op_half_message，并且小于half_message的offset的消息，最后会排序，更新op_half_message的offset。</li>
</ol>
<p>具体的操作见时序图：<br><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102522455673.jpg" alt="-w681"></p>
<h2 id="其他一些优秀的MQ"><a href="#其他一些优秀的MQ" class="headerlink" title="其他一些优秀的MQ"></a>其他一些优秀的MQ</h2><h3 id="滴滴DDMQ"><a href="#滴滴DDMQ" class="headerlink" title="滴滴DDMQ"></a>滴滴DDMQ</h3><blockquote>
<p>开源地址：<a href="https://github.com/didi/DDMQ">https://github.com/didi/DDMQ</a></p>
</blockquote>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102550327265.jpg" alt="-w1211"></p>
<p>滴滴开源的DDMQ，更像是消息队列企业级运营所需要做的组件，比较重。但是功能就很完善，无论是监控策略，还是主从切换，以及内部实现的延迟消息。</p>
<p>优点：</p>
<ol>
<li>实现了Broker的主从切换</li>
<li>实现了跨机房，高可用部署需要HA-Controller</li>
<li>延迟队列使用RocksDB来实现，到时间后重新投递回 RocketMq<br> a. RocksDB使用了LSM树结构，适合于大量写入的业务场景<br> b. <a href="https://www.jianshu.com/p/e1f3189213d5">https://www.jianshu.com/p/e1f3189213d5</a> LSM树介绍 </li>
<li>Proxy层解耦了Consumer和queue，解决了当Consumer大于queue的时候，会有Consumer无法消费的情况。同时，提供了多端（go, php…）接入能力。</li>
</ol>
<h3 id="去哪儿MQ"><a href="#去哪儿MQ" class="headerlink" title="去哪儿MQ"></a>去哪儿MQ</h3><blockquote>
<p>开源地址：<a href="https://github.com/qunarcorp/qmq/blob/master/docs/cn/design.md">https://github.com/qunarcorp/qmq/blob/master/docs/cn/design.md</a></p>
</blockquote>
<p>这个一款设计于RocketMq未开源之前的消息队列，同样采用了顺序写的方案。不同之处，consumer 和 queue 之间，有了一层 pull log。类似于DDMQ的Proxy一层。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102558617023.jpg" alt="-w613"></p>
<p>另外就是通过时间轮，来解决延迟消息插入和读取问题。使得，队列的插入和读取都是 O（1）的。</p>
<p><img src="http://guodhblog.oss-cn-hangzhou.aliyuncs.com/2021/01/10/16102559971296.jpg" alt="-w539"></p>
<h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><ol>
<li><p><strong>消息模型（Message Model）</strong><br>RocketMQ主要由 Producer、Broker、Consumer 三部分组成，其中Producer 负责生产消息，Consumer 负责消费消息，Broker 负责存储消息。Broker 在实际部署过程中对应一台服务器，每个 Broker 可以存储多个Topic的消息，每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址，每个Topic中的消息地址存储于多个 Message Queue 中。ConsumerGroup 由多个Consumer 实例构成。</p>
</li>
<li><p><strong>消息生产者（Producer）</strong><br>负责生产消息，一般由业务系统负责生产消息。一个消息生产者会把业务应用系统里产生的消息发送到broker服务器。RocketMQ提供多种发送方式，同步发送、异步发送、顺序发送、单向发送。同步和异步方式均需要Broker返回确认信息，单向发送不需要。</p>
</li>
<li><p> <strong>消息消费者（Consumer）</strong><br>负责消费消息，一般是后台系统负责异步消费。一个消息消费者会从Broker服务器拉取消息、并将其提供给应用程序。从用户应用的角度而言提供了两种消费形式：拉取式消费、推动式消费。</p>
</li>
<li><p> <strong>主题（Topic）</strong><br>表示一类消息的集合，每个主题包含若干条消息，每条消息只能属于一个主题，是RocketMQ进行消息订阅的基本单位。</p>
</li>
<li><p><strong>代理服务器（Broker Server）</strong><br>消息中转角色，负责存储消息、转发消息。代理服务器在RocketMQ系统中负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。代理服务器也存储消息相关的元数据，包括消费者组、消费进度偏移和主题和队列消息等。</p>
</li>
<li><p><strong>名字服务（Name Server)</strong><br>名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的Broker IP列表。多个Namesrv实例组成集群，但相互独立，没有信息交换。</p>
</li>
<li><p><strong>拉取式消费（Pull Consumer)</strong><br>Consumer消费的一种类型，应用通常主动调用Consumer的拉消息方法从Broker服务器拉消息、主动权由应用控制。一旦获取了批量消息，应用就会启动消费过程。</p>
</li>
<li><p><strong>推动式消费（Push Consumer）</strong><br>Consumer消费的一种类型，该模式下Broker收到数据后会主动推送给消费端，该消费模式一般实时性较高。</p>
</li>
<li><p><strong>生产者组（Producer Group）</strong><br>同一类Producer的集合，这类Producer发送同一类消息且发送逻辑一致。如果发送的是事务消息且原始生产者在发送之后崩溃，则Broker服务器会联系同一生产者组的其他生产者实例以提交或回溯消费。</p>
</li>
<li><p><strong>消费者组（Consumer Group）</strong><br>同一类Consumer的集合，这类Consumer通常消费同一类消息且消费逻辑一致。消费者组使得在消息消费方面，实现负载均衡和容错的目标变得非常容易。要注意的是，消费者组的消费者实例必须订阅完全相同的Topic。RocketMQ 支持两种消息模式：集群消费（Clustering）和广播消费（Broadcasting）。</p>
</li>
<li><p><strong>集群消费（Clustering）</strong><br>集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊消息。</p>
</li>
<li><p><strong>广播消费（Broadcasting）</strong><br>广播消费模式下，相同Consumer Group的每个Consumer实例都接收全量的消息。</p>
</li>
<li><p><strong>普通顺序消息（Normal Ordered Message）</strong><br>普通顺序消费模式下，消费者通过同一个消费队列收到的消息是有顺序的，不同消息队列收到的消息则可能是无顺序的。</p>
</li>
<li><p><strong>严格顺序消息（Strictly Ordered Message）</strong><br>严格顺序消息模式下，消费者收到的所有消息均是有顺序的。</p>
</li>
<li><p><strong>消息（Message）</strong><br>消息系统所传输信息的物理载体，生产和消费数据的最小单位，每条消息必须属于一个主题。RocketMQ中每个消息拥有唯一的Message ID，且可以携带具有业务标识的Key。系统提供了通过Message ID和Key查询消息的功能。</p>
</li>
<li><p><strong>标签（Tag）</strong><br>为消息设置的标志，用于同一主题下区分不同类型的消息。来自同一业务单元的消息，可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性，并优化RocketMQ提供的查询系统。消费者可以根据Tag实现对不同子主题的不同消费逻辑，实现更好的扩展性。</p>
</li>
</ol>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/blog/tags/RocketMq/" rel="tag"># RocketMq</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/blog/2021/01/01/2021/2021%E5%85%83%E6%97%A6/" rel="prev" title="2021年新的开始">
      <i class="fa fa-chevron-left"></i> 2021年新的开始
    </a></div>
      <div class="post-nav-item">
    <a href="/blog/2021/04/01/2021/%E5%86%99%E5%9C%A86%E4%B8%AA%E6%9C%88%E8%BD%AC%E6%AD%A3%E7%9A%84%E6%97%B6%E5%80%99/" rel="next" title="写在6个月转正的时候">
      写在6个月转正的时候 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  

  </div>


          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let activeClass = CONFIG.comments.activeClass;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          Table of Contents
        </li>
        <li class="sidebar-nav-overview">
          Overview
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#RocketMq%E6%9E%B6%E6%9E%84%E5%9B%BE"><span class="nav-number">1.</span> <span class="nav-text">RocketMq架构图</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#RocketMq%E7%9A%84%E4%B8%80%E4%BA%9B%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5"><span class="nav-number">2.</span> <span class="nav-text">RocketMq的一些基本概念</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#MQ-%E7%9A%84%E6%BC%94%E5%8C%96%E5%8E%86%E5%8F%B2"><span class="nav-number">3.</span> <span class="nav-text">MQ 的演化历史</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9C%AC%E8%B4%A8%E6%98%AF%E9%98%9F%E5%88%97"><span class="nav-number">3.1.</span> <span class="nav-text">本质是队列</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%9F%BA%E7%A1%80%E8%83%BD%E5%8A%9B"><span class="nav-number">3.2.</span> <span class="nav-text">基础能力</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#RocketMq%E7%9A%84%E4%B8%80%E4%BA%9BFeature"><span class="nav-number">4.</span> <span class="nav-text">RocketMq的一些Feature</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BB%8EBroker%E8%AF%B4%E8%B5%B7"><span class="nav-number">5.</span> <span class="nav-text">从Broker说起</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E9%AB%98%E6%80%A7%E8%83%BD%E5%86%99%E5%85%A5%EF%BC%9F"><span class="nav-number">5.1.</span> <span class="nav-text">如何保证高性能写入？</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#CommitLog-%E5%86%99%E5%85%A5%E7%A4%BA%E6%84%8F%E5%9B%BE"><span class="nav-number">5.2.</span> <span class="nav-text">CommitLog 写入示意图</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#ConsumerQueue"><span class="nav-number">5.3.</span> <span class="nav-text">ConsumerQueue</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%A0%BC%E5%BC%8F"><span class="nav-number">5.3.1.</span> <span class="nav-text">格式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%94%BE%E5%85%A5%E8%BF%87%E7%A8%8B"><span class="nav-number">5.3.2.</span> <span class="nav-text">放入过程</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%90%BD%E7%9B%98%E4%BD%8D%E7%BD%AE"><span class="nav-number">5.3.3.</span> <span class="nav-text">落盘位置</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Consumer%E7%9A%84Offset%E4%BD%8D%E7%BD%AE%EF%BC%88%E6%B6%88%E8%B4%B9%E8%BF%9B%E5%BA%A6%EF%BC%89"><span class="nav-number">5.3.4.</span> <span class="nav-text">Consumer的Offset位置（消费进度）</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%A4%9A%E4%B8%AAqueue%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%BB%B7%E5%80%BC"><span class="nav-number">5.3.5.</span> <span class="nav-text">多个queue的设计价值</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#PageCache"><span class="nav-number">5.4.</span> <span class="nav-text">PageCache</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#PageCache%E7%9A%84%E5%86%99"><span class="nav-number">5.4.1.</span> <span class="nav-text">PageCache的写</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#PageCache%E7%9A%84%E8%AF%BB"><span class="nav-number">5.4.2.</span> <span class="nav-text">PageCache的读</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#PageCache%E5%B0%8F%E7%BB%93"><span class="nav-number">5.4.3.</span> <span class="nav-text">PageCache小结</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%A0%E9%99%A4%E7%AD%96%E7%95%A5"><span class="nav-number">5.5.</span> <span class="nav-text">删除策略</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Producer"><span class="nav-number">6.</span> <span class="nav-text">Producer</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Index"><span class="nav-number">6.1.</span> <span class="nav-text">Index</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#MessageId"><span class="nav-number">6.2.</span> <span class="nav-text">MessageId</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Consumer"><span class="nav-number">7.</span> <span class="nav-text">Consumer</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#Pull%E6%A8%A1%E5%BC%8F"><span class="nav-number">7.0.1.</span> <span class="nav-text">Pull模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%87%8D%E6%96%B0%E6%8A%95%E9%80%92"><span class="nav-number">7.0.2.</span> <span class="nav-text">重新投递</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Rebalance"><span class="nav-number">7.0.3.</span> <span class="nav-text">Rebalance</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%A1%BA%E5%BA%8F%E6%B6%88%E8%B4%B9"><span class="nav-number">7.0.4.</span> <span class="nav-text">顺序消费</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#tag%E7%9A%84%E4%BD%BF%E7%94%A8"><span class="nav-number">7.0.5.</span> <span class="nav-text">tag的使用</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%AB%98%E5%8F%AF%E7%94%A8%E9%83%A8%E7%BD%B2"><span class="nav-number">8.</span> <span class="nav-text">高可用部署</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%A4%9A-Master-%E6%A8%A1%E5%BC%8F"><span class="nav-number">8.0.1.</span> <span class="nav-text">多 Master 模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%A4%9AMaster%E5%A4%9ASlave%E6%A8%A1%E5%BC%8F-%E5%BC%82%E6%AD%A5%E5%A4%8D%E5%88%B6"><span class="nav-number">8.0.2.</span> <span class="nav-text">多Master多Slave模式-异步复制</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%A4%9AMaster%E5%A4%9ASlave%E6%A8%A1%E5%BC%8F-%E5%90%8C%E6%AD%A5%E5%8F%8C%E5%86%99"><span class="nav-number">8.0.3.</span> <span class="nav-text">多Master多Slave模式-同步双写</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Dledger%E6%A8%A1%E5%BC%8F"><span class="nav-number">8.0.4.</span> <span class="nav-text">Dledger模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Dledger%E6%A8%A1%E5%BC%8F-VS-%E5%A4%9AMaster-Slave%E6%A8%A1%E5%BC%8F"><span class="nav-number">8.0.5.</span> <span class="nav-text">Dledger模式 VS 多Master-Slave模式</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%87%AA%E5%B7%B1%E5%AE%9E%E7%8E%B0%E4%B8%BB%E4%BB%8E%E5%88%87%E6%8D%A2"><span class="nav-number">8.0.6.</span> <span class="nav-text">自己实现主从切换</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E8%B7%A8%E6%9C%BA%E6%88%BF%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8"><span class="nav-number">8.0.7.</span> <span class="nav-text">跨机房的高可用</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BA%8B%E5%8A%A1%E6%B6%88%E6%81%AF"><span class="nav-number">9.</span> <span class="nav-text">事务消息</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#opMessage"><span class="nav-number">9.0.1.</span> <span class="nav-text">opMessage</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%85%B6%E4%BB%96%E4%B8%80%E4%BA%9B%E4%BC%98%E7%A7%80%E7%9A%84MQ"><span class="nav-number">10.</span> <span class="nav-text">其他一些优秀的MQ</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%BB%B4%E6%BB%B4DDMQ"><span class="nav-number">10.1.</span> <span class="nav-text">滴滴DDMQ</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8E%BB%E5%93%AA%E5%84%BFMQ"><span class="nav-number">10.2.</span> <span class="nav-text">去哪儿MQ</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E9%99%84%E5%BD%95"><span class="nav-number">11.</span> <span class="nav-text">附录</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">RunningEgg</p>
  <div class="site-description" itemprop="description">写下我的点点滴滴，写下我对这个世界的看法，还有我内心锁崇拜的技术</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/blog/archives/">
        
          <span class="site-state-item-count">89</span>
          <span class="site-state-item-name">posts</span>
        </a>
      </div>
      <div class="site-state-item site-state-tags">
        <span class="site-state-item-count">24</span>
        <span class="site-state-item-name">tags</span>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="mailto:guodh_1@163.com" title="E-Mail → mailto:guodh_1@163.com" rel="noopener" target="_blank"><i class="fa fa-fw fa-envelope"></i>E-Mail</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">RunningEgg</span>
</div>
  <div class="powered-by">Powered by <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> v5.3.0
  </div>
  <span class="post-meta-divider">|</span>
  <div class="theme-info">Theme – <a href="https://pisces.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Pisces</a> v7.7.1
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/blog/lib/anime.min.js"></script>
  <script src="/blog/lib/velocity/velocity.min.js"></script>
  <script src="/blog/lib/velocity/velocity.ui.min.js"></script>

<script src="/blog/js/utils.js"></script>

<script src="/blog/js/motion.js"></script>


<script src="/blog/js/schemes/pisces.js"></script>


<script src="/blog/js/next-boot.js"></script>




  















  

  

</body>
</html>
